﻿using Red.Controls.DataDriven;
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Interactivity;
using System.Windows.Media;

namespace Red.Controls.Behaviors
{

    /// <summary>
    ///    An enum used to set a modifier for updating a numeric value
    /// </summary>
    public enum ValueUpdateModifier
    {
        Total = 1,
        Decimal = 10,
        Hundredths = 100,
        Thousandths = 1000
    }

    /// <summary>
    ///     A behavior that increases or decreases the float value of the  text box depending on the mouse drag in X and Y
    /// </summary>
    public class ChangeFloatValueWithMouseBehavior : Behavior<NumberBox>
    {
        #region Internal Types

        private class ValueUpdateModeIndicator : Adorner
        {
            private double _position = 0f;
            private double _startPosition = 0f;
            private double _width = 7f;

            // [0-1]
            public double Position
            {
                get { return _position; }
                set
                {
                    if ( value != _position )
                    {
                        _position = value;
                        InvalidateVisual();
                    }
                }
            }

            // [0-1]
            public double StartPosition
            {
                get { return _startPosition; }
                set
                {
                    if ( value != _startPosition )
                    {
                        _startPosition = value;
                        InvalidateVisual();
                    }
                }
            }

            // [0-1]
            public double ElementWidth
            {
                get { return _width; }
                set
                {
                    if ( value != _width )
                    {
                        _width = value;
                        InvalidateVisual();
                    }
                }
            }

            public ValueUpdateModeIndicator( NumberBox adornedElement )
                : base( adornedElement )
            {
            }

            protected override void OnRender( DrawingContext drawingContext )
            {
                var box = (NumberBox)AdornedElement;
                double h = box.ActualHeight;

                Rect r1 = new Rect( _startPosition + _position, 2, _width, 2 );
                Rect r2 = new Rect( _startPosition + _position, h - 4, _width, 2 );
                drawingContext.DrawRectangle( Brushes.Red, null, r1 );
                drawingContext.DrawRectangle( Brushes.Red, null, r2 );
            }
        }

        #endregion Internal Types

        #region Properties

        public static readonly DependencyProperty XUpdateModifierProperty = DependencyProperty.Register( 
            "XUpdateModifier", typeof( ValueUpdateModifier ), typeof( ChangeFloatValueWithMouseBehavior ), new FrameworkPropertyMetadata( ValueUpdateModifier.Decimal ) );
  
        public static readonly DependencyProperty YUpdateModifierProperty = DependencyProperty.Register( 
            "YUpdateModifier", typeof( ValueUpdateModifier ), typeof( ChangeFloatValueWithMouseBehavior ), new FrameworkPropertyMetadata( ValueUpdateModifier.Total ) );  
   
        /// <summary>
        ///    A value modifier on X axis
        /// </summary>
        public ValueUpdateModifier XUpdateModifier
        {
            get { return (ValueUpdateModifier)GetValue( XUpdateModifierProperty ); }
            set { SetValue( XUpdateModifierProperty, value ); }
        }   
        
        /// <summary>
        ///    A value modifier on Y axis
        /// </summary>
		public ValueUpdateModifier YUpdateModifier
        {
            get { return (ValueUpdateModifier)GetValue( YUpdateModifierProperty ); }
            set { SetValue( YUpdateModifierProperty, value ); }
        }

        /// <summary>
        ///     A parameter indicating whether the updating value on mouse drag is finished or not
        /// </summary>
        public static bool IsUpdatingValue
        {
            get;
            set;
        }
        
        #endregion Properties

        #region Data

        private Point _mousePositionAtModeChange;
        private decimal _propertyValueAtModeChange;
        private bool _canChangeValue;
        private ContextMenu _dummyContextMenu = new ContextMenu();
        private ChangeValueMode _lastValueMode;
        private ValueUpdateModeIndicator _indicator;
        private int? _defMinDigitsAfterComma;
        private int _defMaxDigitsAfterComma;

        /// <summary>
        ///    An enum used to set current value update mode - which axis when dragging a mouse is dominant
        /// </summary>
        enum ChangeValueMode
        {
            Both,
            XOnly,
            YOnly
        }

        /// <summary>
        ///    Current value update mode - decides which axis is dominant
        /// </summary>
        private ChangeValueMode ValueMode { get; set; }

        #endregion  Data

        #region Event handlers

        protected override void OnAttached()
        {
            AssociatedObject.PreviewMouseRightButtonDown += ( sender, e ) =>
            {
                _propertyValueAtModeChange = (decimal)AssociatedObject.Value;
                _mousePositionAtModeChange = e.GetPosition( AssociatedObject );
                AssociatedObject.CaptureMouse();
                AssociatedObject.Focus();
                _canChangeValue = true;
                IsUpdatingValue = false;
                AssociatedObject.CaretBrush = new SolidColorBrush(Color.FromArgb(0, 0, 0, 0));
                SetValueFormat();
                e.Handled = true;
            };
             
            AssociatedObject.PreviewMouseRightButtonUp += ( sender, e ) =>
            {
                AssociatedObject.ReleaseMouseCapture();
                _canChangeValue = false;
                AssociatedObject.CaretBrush = null;     
                UpdateModeIndicatorVisibility( false );
                ResetValueFormat();
                e.Handled = IsUpdatingValue;
            };

            AssociatedObject.PreviewMouseMove += ( sender, e ) =>
            {
                if ( _canChangeValue )
                {
                    decimal modifier = 0m;
                    decimal propertyValue = (decimal)AssociatedObject.Value;
                    Point mousePos = e.GetPosition( AssociatedObject );
                    int yModeDiff = (int)( mousePos.Y - _mousePositionAtModeChange.Y );
                    int xModeDiff = (int)( mousePos.X - _mousePositionAtModeChange.X );

                    if ( AssociatedObject.IsMouseCaptured )
                    {                    
                        IsUpdatingValue = true;

                        if ( ValueMode == ChangeValueMode.Both )
                        {
                            if ( Math.Abs( xModeDiff ) >= 1 && Math.Abs( xModeDiff ) > Math.Abs( yModeDiff ) )
                            {
                                ValueMode = ChangeValueMode.XOnly;
                            }
                            else if ( Math.Abs( yModeDiff ) >= 1 && Math.Abs( yModeDiff ) > Math.Abs( xModeDiff ) )
                            {
                                ValueMode = ChangeValueMode.YOnly;
                            }
                            else
                            {
                                ValueMode = _lastValueMode;
                            }
                        }

                        if ( ValueMode == ChangeValueMode.XOnly )
                        {
                            modifier = xModeDiff * (decimal)( 1.0 / (int)XUpdateModifier );
                            propertyValue = _propertyValueAtModeChange + modifier;
                            UpdateMode( yModeDiff, Math.Abs( xModeDiff - yModeDiff ), mousePos, propertyValue );
                        }

                        if ( ValueMode == ChangeValueMode.YOnly )
                        {
                            modifier = yModeDiff * (decimal)( 1.0 / (int)YUpdateModifier );
                            propertyValue = _propertyValueAtModeChange - modifier;
                            UpdateMode( xModeDiff, Math.Abs( yModeDiff - xModeDiff ), mousePos, propertyValue );
                        }

                        AssociatedObject.Value = (float)propertyValue;
                        UpdateModeIndicatorVisibility( true );
                        e.Handled = true;
                    }
                }
            };
        }

        #endregion Event handlers

        #region Private methods

        /// <summary>
        ///    Updates current value update mode depending on the cursor position
        /// </summary>
        private void UpdateMode( float diff, float deltaXY, Point mousePos, decimal propertyValue )
        {
            if ( Math.Abs( diff ) >= 5 && deltaXY > 5 )
            {
                _lastValueMode = ValueMode;
                ValueMode = ChangeValueMode.Both;
                         
                _mousePositionAtModeChange.X = mousePos.X;
                _mousePositionAtModeChange.Y = mousePos.Y;
                _propertyValueAtModeChange = propertyValue;
            }
        }

        private void UpdateModeIndicatorVisibility( bool isVisible )
        {
            var adornerLayer = AdornerLayer.GetAdornerLayer( AssociatedObject );
            if ( adornerLayer != null )
            {
                if ( isVisible )
                {
                    if ( _indicator == null )
                    {
                        _indicator = new ValueUpdateModeIndicator( AssociatedObject );
                        _indicator.StartPosition = AssociatedObject.Padding.Left + AssociatedObject.FontFamily.LineSpacing;
                        adornerLayer.Add( _indicator );  
                    }                
                    UpdateModeIndicator();
                }
                else
                {
                    if ( _indicator != null )
                    {
                        adornerLayer.Remove( _indicator );
                        _indicator = null; 
                    }
                }
            }
        }

        private void UpdateModeIndicator()
        {
            if ( _indicator == null ) 
                return;
         
            string strValue = AssociatedObject.Value.ToString();
            int charPos = GetCharacterIndex( strValue );
           
            if ( strValue.Length > charPos )
            {
                string subStrCharacter = strValue.Substring( charPos, 1 );
                _indicator.ElementWidth = GetFormattedText(subStrCharacter).Width;

                if ( charPos == 0 )
                {
                    _indicator.Position = 0;
                }
                else if ( charPos > 0 )
                {
                    string subStrValue = strValue.Substring( 0, charPos );
                    _indicator.Position = GetFormattedText( subStrValue ).WidthIncludingTrailingWhitespace + AssociatedObject.FontFamily.LineSpacing;
                }
            }
        }

        private FormattedText GetFormattedText( string text )
        {
            return new FormattedText( text, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, new Typeface( AssociatedObject.FontFamily.ToString() ), AssociatedObject.FontSize, AssociatedObject.Foreground );
        }

        private int GetCharacterIndex( string text )
        {
            int charIndex = 0;
            int commaPos = 1;
            ChangeValueMode activeValueMode = ValueMode;

            if ( activeValueMode == ChangeValueMode.Both )
                activeValueMode = _lastValueMode;
            
            if ( activeValueMode == ChangeValueMode.XOnly )
            {
                charIndex = (int)XUpdateModifier;
            }
            else if ( activeValueMode == ChangeValueMode.YOnly )
            {
                charIndex = (int)YUpdateModifier;
            }

            charIndex = charIndex.ToString().Length;
            commaPos = text.IndexOf( '.' );

            if ( commaPos < 0 )
            {
                commaPos = text.Length;
            }

            if ( charIndex == 1 )
            {
                charIndex = commaPos - charIndex;
            }
            else
            {
                charIndex = commaPos + ( charIndex - 1 );
            }

            return charIndex;
        }

        private void SetValueFormat()
        {
            _defMaxDigitsAfterComma = AssociatedObject.MaxDigitsAfterComma;
            _defMinDigitsAfterComma = AssociatedObject.MinDigitsAfterComma;
            int digitsAfterComma;

            if ( (int)XUpdateModifier > (int)YUpdateModifier )
            {
                digitsAfterComma = (int)XUpdateModifier;
            }
            else
            {
                digitsAfterComma = (int)YUpdateModifier;
            }

            digitsAfterComma = digitsAfterComma.ToString().Length - 1;

            if ( AssociatedObject.MinDigitsAfterComma == null || AssociatedObject.MinDigitsAfterComma < digitsAfterComma )
                AssociatedObject.MinDigitsAfterComma = digitsAfterComma;

            if ( AssociatedObject.MinDigitsAfterComma >= AssociatedObject.MaxDigitsAfterComma )
            {
                AssociatedObject.MaxDigitsAfterComma = (int)AssociatedObject.MinDigitsAfterComma + 1;
            }

        }

        private void ResetValueFormat()
        {
            AssociatedObject.MaxDigitsAfterComma =  _defMaxDigitsAfterComma;
            AssociatedObject.MinDigitsAfterComma = _defMinDigitsAfterComma;
        }

        #endregion Private methods
    }
}
